猛火Fierflame

文件系统访问API

说明

通过文件系统 API,网页可以实现对磁盘内容的读写,进而可以实现类似与本地编辑器的效果。当然,作为网页,文件系统的访问也是会受到严格的权限控制的,只能访问用户选择的文件或目录,才能够进行访问。

文件系统访问API包括三个 API:

三个 API 只能在安全的上下文中才能使用,并且必须由用户手动触发。

兼容性

截止目前(2022年2月25日),只有基于 Chrome 86+ 的浏览器(如 Chrome 86、Edge 86)支持文件系统访问API 。兼容性具体见 showOpenFilePicker | Can I use…

使用文件系统访问 API

从本地文件系统读取文件

调用 window.showOpenFilePicker(),会显示一个文件选取器对话框,并提示用户选择文件。选择文件后,API 将返回一个文件句柄数组。可选参数会影响文件选取器的行为,例如允许用户选择多个文件或限制文件类型。默认情况下,文件选取器只允许用户选择单个文件。

用户选择文件后,showOpenFilePicker()返回一个句柄数组,其中包含与该文件交互所需的属性和方法。

拥有文件句柄之后,便可以获取文件属性及读取文件内容。通过调用fileHandle.getFile(),可以得到一个 File 文件对象。 File 继承自 Blob,之后便可以通过.arrayBuffer().text().stream() 等方法获取数据,或者通过 .slice() 方法分割 Blob 对象。

openFileBtn.addEventListener('click', async () => {
  // 让用户选择一个文件
  const [fileHandle] = await window.showOpenFilePicker();
  // 从文件句柄获得文件
  const file = await fileHandle.getFile();
  // 以文本格式读取文件读取文件内容
  const contents = await file.text();
  // 将内容放到 <textarea> 中
  textArea.value = contents;
});

指定建议的启动目录

在许多情况下,你可能希望应用建议默认的位置。例如要构建文本编辑器,则可能需要在默认文档文件夹中启动文件保存或文件打开对话框,而对于图像编辑器,则可能需要在默认图片文件夹中启动。您可以通过 startIn 建议默认的启动目录。

const fileHandle = await self.showOpenFilePicker({
  startIn: 'pictures'
});

已知系统目录的列表是:

除了已知的系统目录之外,还可以将现有文件或目录句柄作为 startIn 的值。然后,该对话框将在同一目录中打开。

将文件写入本地文件系统

要保存文件,需要调用showSaveFilePicker(),它将在“保存”模式下显示文件选取器,允许用户选择要用于保存的新文件。

得到要保存的文件句柄后,便可以将文件保存到磁盘,不过,在此之前,我们需要先通过fileHandle.createWritable() 获得一个 FileSystemWritableFileStream 文件系统可写文件流对象,之后通过 Writable 文件系统可写文件流对象进行文件的保存。

在许多情况下,你可能希望应用建议默认文件名。则可以通过 suggestedName 选项的一部分传递来实现此目的。

async function save(contents) {
  const options = {
    // 建议的文件名
    suggestedName: '新文件.txt',
    // 例如文本编辑器,希望自动添加 .txt 扩展名
    types: [
      {
        description: '文本文件',
        accept: { 'text/plain': ['.txt'] },
      },
    ],
  };
  const fileHandle = await window.showSaveFilePicker(options);
  // 创建一个 FileSystemWritableFileStream 文件系统可写文件流对象
  const writable = await fileHandle.createWritable();
  // 将数据写入流
  await writable.write(contents);
  // 关闭流并将文件保存到磁盘
  await writable.close();
}

注意 在流关闭之前,不会将更改写入磁盘

分用途记录选择器打开的位置

有时,应用具有用于不同目的不同选取器。例如,富文本编辑器可能允许用户打开文本文件,但也允许用户导入图像。默认情况下,每个文件选取器都将在最后记住的位置打开。但可以通过存储每种类型的选取器的值来规避这种情况。如果指定了id 选项,相同 id 的文件选取器用单独一组最后记住的位置。

const textFileHandle = await self.showSaveFilePicker({
  id: 'text',
});

const imageFileHandle = await self.showSaveFilePicker({
  id: 'image',
});

打开目录并枚举其内容

要枚举目录中的所有文件,需要调用showDirectoryPicker()为用户提供一个目录选取器来选取目录,再进行枚举。

const butDir = document.getElementById('butDirectory');
butDir.addEventListener('click', async () => {
  const dirHandle = await window.showDirectoryPicker();
  for await (const entry of dirHandle.values()) {
    console.log(entry.kind, entry.name);
  }
});

创建、访问、删除目录中的文件和目录及解析

通过目录的getFileHandle(path, options) getDirectoryHandle(path, options) 方法分别用来获取文件及子目录,如果第二个参数中的 create 选项为 true,则可以创建文件或目录。

通过目录的removeEntry(path, options)方法来删除文件及子目录,如果第二个参数中的 recursive 选项为 true,则可以进行递归删除。

解析目录中项目的路径

通过目录的resolve(handle) 方法,可以得到解析相关项目的路径。

存储的文件或目录句柄和权限

由于权限不在会话之间保留,因此应通过fileHandle.queryPermission()验证用户是否已授予对文件或目录的权限。如果没有,则需要通过 fileHandle.requestPermission() 授权。如下面的verifyPermission,就实现了此功能。

async function verifyPermission(fileHandle, readWrite) {
  const options = {};
  if (readWrite) {
    options.mode = 'readwrite';
  }
  // 检查是否拥有权限,如果拥有,则返回 true
  if ((await fileHandle.queryPermission(options)) === 'granted') {
    return true;
  }
  // 请求授权,若授权,则返回 true
  if ((await fileHandle.requestPermission(options)) === 'granted') {
    return true;
  }
  // 用户没有授权,返回 false
  return false;
}

拖放集成

HTML 拖放界面使 Web 应用程序能够接受在网页上拖放的文件。在拖放操作中,拖动的文件项和目录可以通过DataTransferItem.getAsFileSystemHandle()获得。下面的清单显示了这一点的实际应用。请注意,拖放界面的 DataTransferItem.kind,文件和目录的均为'file',而文件系统访问 API 的 FileSystemHandle.kind,文件和目录则分别为'file''directory'

elem.addEventListener('dragover', (e) => {
  // 禁用默认操作
  e.preventDefault();
});

elem.addEventListener('drop', async (e) => {
  e.preventDefault();

  const fileHandlesPromises = [...e.dataTransfer.items]
    // 过滤只保留文件/目录对应的拖拽项
    .filter((item) => item.kind === 'file')
    // 获得对应的文件句柄和目录句柄
    .map((item) => item.getAsFileSystemHandle());

  for await (const handle of fileHandlesPromises) {
    if (handle.kind === 'directory') {
      console.log(`Directory: ${handle.name}`);
    } else {
      console.log(`File: ${handle.name}`);
    }
  }
});

在 IndexedDB 中存储文件句柄或目录句柄

文件句柄和目录句柄是可序列化的,这意味着您可以将文件句柄或目录句柄保存到 IndexedDB,也可以调用 postMessage以在同一顶级源之间发送它们。具体操作这里将不再赘述,可以查阅其他相关文档或教程。

API 介绍

async function showOpenFilePicker()

参数

返回值

FileSystemFileHandle 文件句柄对象构成的数组

异常

async function showSaveFilePicker()

参数

返回值

一个 FileSystemFileHandle 文件句柄对象

异常

async function showDirectoryPicker()

参数

返回值

一个 FileSystemDirectoryHandle 目录句柄对象

异常

FileSystemFileHandle 文件句柄对象

属性

继承了 FileSystemHandle 文件系统句柄对象的属性

方法

继承了 FileSystemHandle 文件系统句柄对象的方法

FileSystemDirectoryHandle 目录句柄对象

属性

继承了 FileSystemHandle 文件系统句柄对象的属性

方法

继承了 FileSystemHandle 文件系统句柄对象的方法

FileSystemHandle 文件系统句柄对象

属性

方法

上一篇:ECMAScript 装饰器(阶段3)
下一篇:HTML5 全屏 API